<?php

/**
 * JPGraph v4.0.3
 */

namespace lemon\JpGraph\Graph;

require_once __DIR__ . '/../config.inc.php';

use lemon\JpGraph\Image;
use lemon\JpGraph\Plot;
use lemon\JpGraph\Text;
use lemon\JpGraph\Util;

/**
 * @class Graph
 * // Description: Main class to handle graphs
 */
class Graph
{
	public $gDateLocale;
	public $gJpgDateLocale;
	public $cache; // Cache object (singleton)
	public $img; // Img object (singleton)
	public $plots   = []; // Array of all plot object in the graph (for Y 1 axis)
	public $y2plots = []; // Array of all plot object in the graph (for Y 2 axis)
	public $ynplots = [];
	public $xscale; // X Scale object (could be instance of LinearScale or LogScale
	public $yscale;
	public $y2scale;
	public $ynscale = [];
	public $iIcons  = []; // Array of Icons to add to
	public $cache_name; // File name to be used for the current graph in the cache directory
	public $xgrid; // X Grid object (linear or logarithmic)
	public $ygrid;
	public $y2grid; //dito for Y
	public $doframe;
	public $frame_color;
	public $frame_weight; // Frame around graph
	public $boxed		= false;
	public $box_color	= 'black';
	public $box_weight   = 1; // Box around plot area
	public $doshadow	 = false;
	public $shadow_width = 4;
	public $shadow_color = 'gray@0.5'; // Shadow for graph
	public $xaxis; // X-axis (instane of Axis class)
	public $yaxis;
	public $y2axis;
	public $ynaxis = []; // Y axis (instance of Axis class)
	public $margin_color; // Margin color of graph
	public $plotarea_color = [255, 255, 255]; // Plot area color
	public $title;
	public $subtitle;
	public $subsubtitle; // Title and subtitle(s) text object
	public $axtype = 'linlin'; // Type of axis
	public $xtick_factor;
	public $ytick_factor; // Factor to determine the maximum number of ticks depending on the plot width
	public $texts;
	public $y2texts; // Text object to ge shown in the graph
	public $lines;
	public $y2lines;
	public $bands;
	public $y2bands;
	public $text_scale_off		  = 0;
	public $text_scale_abscenteroff = -1; // Text scale in fractions and for centering bars
	public $background_image		= '';
	public $background_image_type   = -1;
	public $background_image_format = 'png';
	public $background_image_bright = 0;
	public $background_image_contr  = 0;
	public $background_image_sat	= 0;
	public $background_image_xpos   = 0;
	public $background_image_ypos   = 0;
	public $image_bright			= 0;
	public $image_contr			 = 0;
	public $image_sat			   = 0;
	public $inline;
	public $showcsim	 = 0;
	public $csimcolor	= 'red'; //debug stuff, draw the csim boundaris on the image if <>0
	public $grid_depth   = DEPTH_BACK; // Draw grid under all plots as default
	public $iAxisStyle   = AXSTYLE_SIMPLE;
	public $iCSIMdisplay = false;
	public $iHasStroked  = false;
	public $footer;
	public $csimcachename	= '';
	public $csimcachetimeout = 0;
	public $iCSIMImgAlt	  = '';
	public $iDoClipping	  = false;
	public $y2orderback	  = true;
	public $tabtitle;
	public $bkg_gradtype   = -1;
	public $bkg_gradstyle  = BGRAD_MARGIN;
	public $bkg_gradfrom   = 'navy';
	public $bkg_gradto	 = 'silver';
	public $plot_gradtype  = -1;
	public $plot_gradstyle = BGRAD_MARGIN;
	public $plot_gradfrom  = 'silver';
	public $plot_gradto	= 'navy';

	public $titlebackground	   = false;
	public $titlebackground_color = 'lightblue';
	public $titlebackground_style = 1;
	public $titlebackground_framecolor;
	public $titlebackground_framestyle;
	public $titlebackground_frameweight;
	public $titlebackground_bevelheight;
	public $titlebkg_fillstyle = TITLEBKG_FILLSTYLE_SOLID;
	public $titlebkg_scolor1   = 'black';
	public $titlebkg_scolor2   = 'white';
	public $framebevel;
	public $framebeveldepth;
	public $framebevelborder;
	public $framebevelbordercolor;
	public $framebevelcolor1;
	public $framebevelcolor2;
	public $background_image_mix  = 100;
	public $background_cflag	  = '';
	public $background_cflag_type = BGIMG_FILLPLOT;
	public $background_cflag_mix  = 100;
	public $iImgTrans			 = false;
	public $iImgTransHorizon	  = 100;
	public $iImgTransSkewDist	 = 150;
	public $iImgTransDirection	= 1;
	public $iImgTransMinSize	  = true;
	public $iImgTransFillColor	= 'white';
	public $iImgTransHighQ		= false;
	public $iImgTransBorder	   = false;
	public $iImgTransHorizonPos   = 0.5;
	public $legend;
	public $graph_theme;
	protected $iYAxisDeltaPos	   = 50;
	protected $iIconDepth		   = DEPTH_BACK;
	protected $iAxisLblBgType	   = 0;
	protected $iXAxisLblBgFillColor = 'lightgray';
	protected $iXAxisLblBgColor	 = 'black';
	protected $iYAxisLblBgFillColor = 'lightgray';
	protected $iYAxisLblBgColor	 = 'black';
	protected $iTables;

	protected $isRunningClear = false;
	protected $inputValues;
	protected $isAfterSetScale = false;

	// aWIdth   Width in pixels of image
	// aHeight   Height in pixels of image
	// aCachedName Name for image file in cache directory
	// aTimeOut  Timeout in minutes for image in cache
	// aInline  If true the image is streamed back in the call to Stroke()
	//   If false the image is just created in the cache
	public function __construct($aWidth = 300, $aHeight = 200, $aCachedName = '', $aTimeout = 0, $aInline = true){
		$this->gDateLocale	= new Util\DateLocale();
		$this->gJpgDateLocale = new Util\DateLocale();
		if (!is_numeric($aWidth) || !is_numeric($aHeight)) {
			Util\JpGraphError::RaiseL(25008); //('Image width/height argument in Graph::Graph() must be numeric');
		}

		// Initialize frame and margin
		$this->InitializeFrameAndMargin();

		// Automatically generate the image file name based on the name of the script that
		// generates the graph
		if ($aCachedName == 'auto') {
			$aCachedName = Util\Helper::GenImgName();
		}

		// Should the image be streamed back to the browser or only to the cache?
		$this->inline = $aInline;

		$this->img   = new Image\RotImage($aWidth, $aHeight);
		$this->cache = new Image\ImgStreamCache();

		// Window doesn't like '?' in the file name so replace it with an '_'
		$aCachedName = str_replace('?', '_', $aCachedName);
		$this->SetupCache($aCachedName, $aTimeout);

		$this->title = new Text\Text();
		$this->title->ParagraphAlign('center');
		$this->title->SetFont(FF_DEFAULT, FS_NORMAL); //FF_FONT2, FS_BOLD
		$this->title->SetMargin(5);
		$this->title->SetAlign('center');

		$this->subtitle = new Text\Text();
		$this->subtitle->ParagraphAlign('center');
		$this->subtitle->SetMargin(3);
		$this->subtitle->SetAlign('center');

		$this->subsubtitle = new Text\Text();
		$this->subsubtitle->ParagraphAlign('center');
		$this->subsubtitle->SetMargin(3);
		$this->subsubtitle->SetAlign('center');

		$this->legend = new Legend();
		$this->footer = new Image\Footer();

		// If the cached version exist just read it directly from the
		// cache, stream it back to browser and exit
		if ($aCachedName != '' && READ_CACHE && $aInline) {
			if ($this->cache->GetAndStream($this->img, $aCachedName)) {
				exit();
			}
		}

		$this->SetTickDensity(); // Normal density

		$this->tabtitle = new Text\GraphTabTitle();

		if (!$this->isRunningClear) {
			$this->inputValues				= [];
			$this->inputValues['aWidth']	  = $aWidth;
			$this->inputValues['aHeight']	 = $aHeight;
			$this->inputValues['aCachedName'] = $aCachedName;
			$this->inputValues['aTimeout']	= $aTimeout;
			$this->inputValues['aInline']	 = $aInline;

			$theme_class = '\lemon\JpGraph\Themes\\' . DEFAULT_THEME_CLASS;

			if (class_exists($theme_class)) {
				$this->graph_theme = new $theme_class();
			}
		}
	}

	public function InitializeFrameAndMargin(){
		$this->doframe	  = true;
		$this->frame_color  = 'black';
		$this->frame_weight = 1;

		$this->titlebackground_framecolor  = 'blue';
		$this->titlebackground_framestyle  = 2;
		$this->titlebackground_frameweight = 1;
		$this->titlebackground_bevelheight = 3;
		$this->titlebkg_fillstyle		  = TITLEBKG_FILLSTYLE_SOLID;
		$this->titlebkg_scolor1			= 'black';
		$this->titlebkg_scolor2			= 'white';
		$this->framebevel				  = false;
		$this->framebeveldepth			 = 2;
		$this->framebevelborder			= false;
		$this->framebevelbordercolor	   = 'black';
		$this->framebevelcolor1			= 'white@0.4';
		$this->framebevelcolor2			= 'black@0.4';

		$this->margin_color = [250, 250, 250];
	}

	public function SetupCache($aFilename, $aTimeout = 60){
		$this->cache_name = $aFilename;
		$this->cache->SetTimeOut($aTimeout);
	}

	// Enable final image perspective transformation
	public function Set3DPerspective($aDir = 1, $aHorizon = 100, $aSkewDist = 120, $aQuality = false, $aFillColor = '#FFFFFF', $aBorder = false, $aMinSize = true, $aHorizonPos = 0.5){
		$this->iImgTrans		   = true;
		$this->iImgTransHorizon	= $aHorizon;
		$this->iImgTransSkewDist   = $aSkewDist;
		$this->iImgTransDirection  = $aDir;
		$this->iImgTransMinSize	= $aMinSize;
		$this->iImgTransFillColor  = $aFillColor;
		$this->iImgTransHighQ	  = $aQuality;
		$this->iImgTransBorder	 = $aBorder;
		$this->iImgTransHorizonPos = $aHorizonPos;
	}

	public function SetUserFont($aNormal, $aBold = '', $aItalic = '', $aBoldIt = ''){
		$this->img->ttf->SetUserFont($aNormal, $aBold, $aItalic, $aBoldIt);
	}

	public function SetUserFont1($aNormal, $aBold = '', $aItalic = '', $aBoldIt = ''){
		$this->img->ttf->SetUserFont1($aNormal, $aBold, $aItalic, $aBoldIt);
	}

	public function SetUserFont2($aNormal, $aBold = '', $aItalic = '', $aBoldIt = ''){
		$this->img->ttf->SetUserFont2($aNormal, $aBold, $aItalic, $aBoldIt);
	}

	public function SetUserFont3($aNormal, $aBold = '', $aItalic = '', $aBoldIt = ''){
		$this->img->ttf->SetUserFont3($aNormal, $aBold, $aItalic, $aBoldIt);
	}

	// Set Image format and optional quality
	public function SetImgFormat($aFormat, $aQuality = 75){
		$this->img->SetImgFormat($aFormat, $aQuality);
	}

	// Should the grid be in front or back of the plot?
	public function SetGridDepth($aDepth){
		$this->grid_depth = $aDepth;
	}

	public function SetIconDepth($aDepth){
		$this->iIconDepth = $aDepth;
	}

	// Specify graph angle 0-360 degrees.
	public function SetAngle($aAngle){
		$this->img->SetAngle($aAngle);
	}

	public function SetAlphaBlending($aFlg = true){
		$this->img->SetAlphaBlending($aFlg);
	}

	// Shortcut to image margin
	public function SetMargin($lm, $rm, $tm, $bm){
		$this->img->SetMargin($lm, $rm, $tm, $bm);
	}

	public function SetY2OrderBack($aBack = true){
		$this->y2orderback = $aBack;
	}

	// Rotate the graph 90 degrees and set the margin
	// when we have done a 90 degree rotation
	public function Set90AndMargin($lm = 0, $rm = 0, $tm = 0, $bm = 0){
		$lm = $lm == 0 ? floor(0.2 * $this->img->width) : $lm;
		$rm = $rm == 0 ? floor(0.1 * $this->img->width) : $rm;
		$tm = $tm == 0 ? floor(0.2 * $this->img->height) : $tm;
		$bm = $bm == 0 ? floor(0.1 * $this->img->height) : $bm;

		$adj = ($this->img->height - $this->img->width) / 2;
		$this->img->SetMargin($tm - $adj, $bm - $adj, $rm + $adj, $lm + $adj);
		$this->img->SetCenter(floor($this->img->width / 2), floor($this->img->height / 2));
		$this->SetAngle(90);
		if (empty($this->yaxis) || empty($this->xaxis)) {
			Util\JpGraphError::RaiseL(25009); //('You must specify what scale to use with a call to Graph::SetScale()');
		}
		$this->xaxis->SetLabelAlign('right', 'center');
		$this->yaxis->SetLabelAlign('center', 'bottom');
	}

	public function SetClipping($aFlg = true){
		$this->iDoClipping = $aFlg;
	}

	// Add a plot object to the graph
	public function Add($aPlot){
		if ($aPlot == null) {
			Util\JpGraphError::RaiseL(25010); //("Graph::Add() You tried to add a null plot to the graph.");
		}
		if (is_array($aPlot) && safe_count($aPlot) > 0) {
			$cl = $aPlot[0];
		} else {
			$cl = $aPlot;
		}

		if ($cl instanceof Text\Text) {
			$this->AddText($aPlot);
		} elseif (($cl instanceof Plot\PlotLine)) {
			$this->AddLine($aPlot);
		} elseif (($cl instanceof Plot\PlotBand)) {
			$this->AddBand($aPlot);
		} elseif (($cl instanceof Plot\IconPlot)) {
			$this->AddIcon($aPlot);
		} elseif (($cl instanceof Text\GTextTable)) {
			$this->AddTable($aPlot);
		} else {
			if (is_array($aPlot)) {
				$this->plots = array_merge($this->plots, $aPlot);
			} else {
				$this->plots[] = $aPlot;
			}
		}

		if ($this->graph_theme) {
			$this->graph_theme->SetupPlot($aPlot);
		}
	}

	public function AddTable($aTable){
		if (is_array($aTable)) {
			for ($i = 0; $i < safe_count($aTable); ++$i) {
				$this->iTables[] = $aTable[$i];
			}
		} else {
			$this->iTables[] = $aTable;
		}
	}

	public function AddIcon($aIcon){
		if (is_array($aIcon)) {
			for ($i = 0; $i < safe_count($aIcon); ++$i) {
				$this->iIcons[] = $aIcon[$i];
			}
		} else {
			$this->iIcons[] = $aIcon;
		}
	}

	// Add plot to second Y-scale
	public function AddY2($aPlot){
		if ($aPlot == null) {
			Util\JpGraphError::RaiseL(25011); //("Graph::AddY2() You tried to add a null plot to the graph.");
		}

		if (is_array($aPlot) && safe_count($aPlot) > 0) {
			$cl = $aPlot[0];
		} else {
			$cl = $aPlot;
		}

		if ($cl instanceof Text\Text) {
			$this->AddText($aPlot, true);
		} elseif (($cl instanceof Plot\PlotLine)) {
			$this->AddLine($aPlot, true);
		} elseif (($cl instanceof Plot\PlotBand)) {
			$this->AddBand($aPlot, true);
		} else {
			$this->y2plots[] = $aPlot;
		}

		if ($this->graph_theme) {
			$this->graph_theme->SetupPlot($aPlot);
		}
	}

	// Add plot to the extra Y-axises
	public function AddY($aN, $aPlot){
		if ($aPlot == null) {
			Util\JpGraphError::RaiseL(25012); //("Graph::AddYN() You tried to add a null plot to the graph.");
		}

		if (is_array($aPlot) && safe_count($aPlot) > 0) {
			$cl = $aPlot[0];
		} else {
			$cl = $aPlot;
		}

		if (($cl instanceof Text\Text) ||
			($cl instanceof Plot\PlotLine) ||
			($cl instanceof Plot\PlotBand)) {
			Util\JpGraphError::RaiseL(25013); //('You can only add standard plots to multiple Y-axis');
		} else {
			$this->ynplots[$aN][] = $aPlot;
		}

		if ($this->graph_theme) {
			$this->graph_theme->SetupPlot($aPlot);
		}
	}

	// Add text object to the graph
	public function AddText($aTxt, $aToY2 = false){
		if ($aTxt == null) {
			Util\JpGraphError::RaiseL(25014); //("Graph::AddText() You tried to add a null text to the graph.");
		}
		if ($aToY2) {
			if (is_array($aTxt)) {
				for ($i = 0; $i < safe_count($aTxt); ++$i) {
					$this->y2texts[] = $aTxt[$i];
				}
			} else {
				$this->y2texts[] = $aTxt;
			}
		} else {
			if (is_array($aTxt)) {
				for ($i = 0; $i < safe_count($aTxt); ++$i) {
					$this->texts[] = $aTxt[$i];
				}
			} else {
				$this->texts[] = $aTxt;
			}
		}
	}

	// Add a line object (class PlotLine) to the graph
	public function AddLine($aLine, $aToY2 = false){
		if ($aLine == null) {
			Util\JpGraphError::RaiseL(25015); //("Graph::AddLine() You tried to add a null line to the graph.");
		}

		if ($aToY2) {
			if (is_array($aLine)) {
				for ($i = 0; $i < safe_count($aLine); ++$i) {
					//$this->y2lines[]=$aLine[$i];
					$this->y2plots[] = $aLine[$i];
				}
			} else {
				//$this->y2lines[] = $aLine;
				$this->y2plots[] = $aLine;
			}
		} else {
			if (is_array($aLine)) {
				for ($i = 0; $i < safe_count($aLine); ++$i) {
					//$this->lines[]=$aLine[$i];
					$this->plots[] = $aLine[$i];
				}
			} else {
				//$this->lines[] = $aLine;
				$this->plots[] = $aLine;
			}
		}
	}

	// Add vertical or horizontal band
	public function AddBand($aBand, $aToY2 = false){
		if ($aBand == null) {
			Util\JpGraphError::RaiseL(25016); //(" Graph::AddBand() You tried to add a null band to the graph.");
		}

		if ($aToY2) {
			if (is_array($aBand)) {
				for ($i = 0; $i < safe_count($aBand); ++$i) {
					$this->y2bands[] = $aBand[$i];
				}
			} else {
				$this->y2bands[] = $aBand;
			}
		} else {
			if (is_array($aBand)) {
				for ($i = 0; $i < safe_count($aBand); ++$i) {
					$this->bands[] = $aBand[$i];
				}
			} else {
				$this->bands[] = $aBand;
			}
		}
	}

	public function SetPlotGradient($aFrom = 'navy', $aTo = 'silver', $aGradType = 2){
		$this->plot_gradtype = $aGradType;
		$this->plot_gradfrom = $aFrom;
		$this->plot_gradto   = $aTo;
	}

	public function SetBackgroundGradient($aFrom = 'navy', $aTo = 'silver', $aGradType = 2, $aStyle = BGRAD_FRAME){
		$this->bkg_gradtype  = $aGradType;
		$this->bkg_gradstyle = $aStyle;
		$this->bkg_gradfrom  = $aFrom;
		$this->bkg_gradto	= $aTo;
	}

	// Set a country flag in the background
	public function SetBackgroundCFlag($aName, $aBgType = BGIMG_FILLPLOT, $aMix = 100){
		$this->background_cflag	  = $aName;
		$this->background_cflag_type = $aBgType;
		$this->background_cflag_mix  = $aMix;
	}

	// Alias for the above method
	public function SetBackgroundCountryFlag($aName, $aBgType = BGIMG_FILLPLOT, $aMix = 100){
		$this->background_cflag	  = $aName;
		$this->background_cflag_type = $aBgType;
		$this->background_cflag_mix  = $aMix;
	}

	// Specify a background image
	public function SetBackgroundImage($aFileName, $aBgType = BGIMG_FILLPLOT, $aImgFormat = 'auto'){
		// Get extension to determine image type
		if ($aImgFormat == 'auto') {
			$e = explode('.', $aFileName);
			if (empty($e)) {
				Util\JpGraphError::RaiseL(25018, $aFileName); //('Incorrect file name for Graph::SetBackgroundImage() : '.$aFileName.' Must have a valid image extension (jpg,gif,png) when using autodetection of image type');
			}

			$valid_formats = ['png', 'jpg', 'gif'];
			$aImgFormat	= strtolower($e[count($e) - 1]);
			if ($aImgFormat == 'jpeg') {
				$aImgFormat = 'jpg';
			} elseif (!in_array($aImgFormat, $valid_formats, true)) {
				Util\JpGraphError::RaiseL(25019, $aImgFormat); //('Unknown file extension ($aImgFormat) in Graph::SetBackgroundImage() for filename: '.$aFileName);
			}
		}

		$this->background_image		= $aFileName;
		$this->background_image_type   = $aBgType;
		$this->background_image_format = $aImgFormat;
	}

	public function SetBackgroundImageMix($aMix){
		$this->background_image_mix = $aMix;
	}

	// Adjust background image position
	public function SetBackgroundImagePos($aXpos, $aYpos){
		$this->background_image_xpos = $aXpos;
		$this->background_image_ypos = $aYpos;
	}

	// Specify axis style (boxed or single)
	public function SetAxisStyle($aStyle){
		$this->iAxisStyle = $aStyle;
	}

	// Set a frame around the plot area
	public function SetBox($aDrawPlotFrame = true, $aPlotFrameColor = [0, 0, 0], $aPlotFrameWeight = 1){
		$this->boxed	  = $aDrawPlotFrame;
		$this->box_weight = $aPlotFrameWeight;
		$this->box_color  = $aPlotFrameColor;
	}

	// Specify color for the plotarea (not the margins)
	public function SetColor($aColor){
		$this->plotarea_color = $aColor;
	}

	// Specify color for the margins (all areas outside the plotarea)
	public function SetMarginColor($aColor){
		$this->margin_color = $aColor;
	}

	// Set a frame around the entire image
	public function SetFrame($aDrawImgFrame = true, $aImgFrameColor = [0, 0, 0], $aImgFrameWeight = 1){
		$this->doframe	  = $aDrawImgFrame;
		$this->frame_color  = $aImgFrameColor;
		$this->frame_weight = $aImgFrameWeight;
	}

	public function SetFrameBevel($aDepth = 3, $aBorder = false, $aBorderColor = 'black', $aColor1 = 'white@0.4', $aColor2 = 'darkgray@0.4', $aFlg = true){
		$this->framebevel			= $aFlg;
		$this->framebeveldepth	   = $aDepth;
		$this->framebevelborder	  = $aBorder;
		$this->framebevelbordercolor = $aBorderColor;
		$this->framebevelcolor1	  = $aColor1;
		$this->framebevelcolor2	  = $aColor2;

		$this->doshadow = false;
	}

	// Set the shadow around the whole image
	public function SetShadow($aShowShadow = true, $aShadowWidth = 5, $aShadowColor = 'darkgray'){
		$this->doshadow	 = $aShowShadow;
		$this->shadow_color = $aShadowColor;
		$this->shadow_width = $aShadowWidth;
		$this->footer->iBottomMargin += $aShadowWidth;
		$this->footer->iRightMargin += $aShadowWidth;
	}

	// Specify x,y scale. Note that if you manually specify the scale
	// you must also specify the tick distance with a call to Ticks::Set()
	public function SetScale($aAxisType, $aYMin = 1, $aYMax = 1, $aXMin = 1, $aXMax = 1){
		$this->axtype = $aAxisType;

		if ($aYMax < $aYMin || $aXMax < $aXMin) {
			Util\JpGraphError::RaiseL(25020); //('Graph::SetScale(): Specified Max value must be larger than the specified Min value.');
		}

		$yt = substr($aAxisType, -3, 3);
		if ($yt == 'lin') {
			$this->yscale = new LinearScale($aYMin, $aYMax);
		} elseif ($yt == 'int') {
			$this->yscale = new LinearScale($aYMin, $aYMax);
			$this->yscale->SetIntScale();
		} elseif ($yt == 'log') {
			$this->yscale = new LogScale($aYMin, $aYMax);
		} else {
			Util\JpGraphError::RaiseL(25021, $aAxisType); //("Unknown scale specification for Y-scale. ($aAxisType)");
		}

		$xt = substr($aAxisType, 0, 3);
		if ($xt == 'lin' || $xt == 'tex') {
			$this->xscale			= new LinearScale($aXMin, $aXMax, 'x');
			$this->xscale->textscale = ($xt == 'tex');
		} elseif ($xt == 'int') {
			$this->xscale = new LinearScale($aXMin, $aXMax, 'x');
			$this->xscale->SetIntScale();
		} elseif ($xt == 'dat') {
			$this->xscale = new DateScale($aXMin, $aXMax, 'x');
		} elseif ($xt == 'log') {
			$this->xscale = new LogScale($aXMin, $aXMax, 'x');
		} else {
			Util\JpGraphError::RaiseL(25022, $aAxisType); //(" Unknown scale specification for X-scale. ($aAxisType)");
		}

		$this->xaxis = new Axis($this->img, $this->xscale);
		$this->yaxis = new Axis($this->img, $this->yscale);
		$this->xgrid = new Grid($this->xaxis);
		$this->ygrid = new Grid($this->yaxis);
		$this->ygrid->Show();

		if (!$this->isRunningClear) {
			$this->inputValues['aAxisType'] = $aAxisType;
			$this->inputValues['aYMin']	 = $aYMin;
			$this->inputValues['aYMax']	 = $aYMax;
			$this->inputValues['aXMin']	 = $aXMin;
			$this->inputValues['aXMax']	 = $aXMax;

			if ($this->graph_theme) {
				$this->graph_theme->ApplyGraph($this);
			}
		}

		$this->isAfterSetScale = true;
	}

	// Specify secondary Y scale
	public function SetY2Scale($aAxisType = 'lin', $aY2Min = 1, $aY2Max = 1){
		if ($aAxisType == 'lin') {
			$this->y2scale = new LinearScale($aY2Min, $aY2Max);
		} elseif ($aAxisType == 'int') {
			$this->y2scale = new LinearScale($aY2Min, $aY2Max);
			$this->y2scale->SetIntScale();
		} elseif ($aAxisType == 'log') {
			$this->y2scale = new LogScale($aY2Min, $aY2Max);
		} else {
			Util\JpGraphError::RaiseL(25023, $aAxisType); //("JpGraph: Unsupported Y2 axis type: $aAxisType\nMust be one of (lin,log,int)");
		}

		$this->y2axis = new Axis($this->img, $this->y2scale);
		$this->y2axis->scale->ticks->SetDirection(SIDE_LEFT);
		$this->y2axis->SetLabelSide(SIDE_RIGHT);
		$this->y2axis->SetPos('max');
		$this->y2axis->SetTitleSide(SIDE_RIGHT);

		// Deafult position is the max x-value
		$this->y2grid = new Grid($this->y2axis);

		if ($this->graph_theme) {
			$this->graph_theme->ApplyGraph($this);
		}
	}

	// Set the delta position (in pixels) between the multiple Y-axis
	public function SetYDeltaDist($aDist){
		$this->iYAxisDeltaPos = $aDist;
	}

	// Specify secondary Y scale
	public function SetYScale($aN, $aAxisType = 'lin', $aYMin = 1, $aYMax = 1){
		if ($aAxisType == 'lin') {
			$this->ynscale[$aN] = new LinearScale($aYMin, $aYMax);
		} elseif ($aAxisType == 'int') {
			$this->ynscale[$aN] = new LinearScale($aYMin, $aYMax);
			$this->ynscale[$aN]->SetIntScale();
		} elseif ($aAxisType == 'log') {
			$this->ynscale[$aN] = new LogScale($aYMin, $aYMax);
		} else {
			Util\JpGraphError::RaiseL(25024, $aAxisType); //("JpGraph: Unsupported Y axis type: $aAxisType\nMust be one of (lin,log,int)");
		}

		$this->ynaxis[$aN] = new Axis($this->img, $this->ynscale[$aN]);
		$this->ynaxis[$aN]->scale->ticks->SetDirection(SIDE_LEFT);
		$this->ynaxis[$aN]->SetLabelSide(SIDE_RIGHT);

		if ($this->graph_theme) {
			$this->graph_theme->ApplyGraph($this);
		}
	}

	// Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
	// The dividing factor have been determined heuristically according to my aesthetic
	// sense (or lack off) y.m.m.v !
	public function SetTickDensity($aYDensity = TICKD_NORMAL, $aXDensity = TICKD_NORMAL){
		$this->xtick_factor = 30;
		$this->ytick_factor = 25;
		switch ($aYDensity) {
			case TICKD_DENSE:
				$this->ytick_factor = 12;

				break;
			case TICKD_NORMAL:
				$this->ytick_factor = 25;

				break;
			case TICKD_SPARSE:
				$this->ytick_factor = 40;

				break;
			case TICKD_VERYSPARSE:
				$this->ytick_factor = 100;

				break;
			default:
				Util\JpGraphError::RaiseL(25025, $densy); //("JpGraph: Unsupported Tick density: $densy");
		}
		switch ($aXDensity) {
			case TICKD_DENSE:
				$this->xtick_factor = 15;

				break;
			case TICKD_NORMAL:
				$this->xtick_factor = 30;

				break;
			case TICKD_SPARSE:
				$this->xtick_factor = 45;

				break;
			case TICKD_VERYSPARSE:
				$this->xtick_factor = 60;

				break;
			default:
				Util\JpGraphError::RaiseL(25025, $densx); //("JpGraph: Unsupported Tick density: $densx");
		}
	}

	// Get a string of all image map areas
	public function GetCSIMareas(){
		if (!$this->iHasStroked) {
			$this->Stroke(_CSIM_SPECIALFILE);
		}

		$csim = $this->title->GetCSIMAreas();
		$csim .= $this->subtitle->GetCSIMAreas();
		$csim .= $this->subsubtitle->GetCSIMAreas();
		$csim .= $this->legend->GetCSIMAreas();

		if ($this->y2axis != null) {
			$csim .= $this->y2axis->title->GetCSIMAreas();
		}

		if ($this->texts != null) {
			$n = safe_count($this->texts);
			for ($i = 0; $i < $n; ++$i) {
				$csim .= $this->texts[$i]->GetCSIMAreas();
			}
		}

		if ($this->y2texts != null && $this->y2scale != null) {
			$n = safe_count($this->y2texts);
			for ($i = 0; $i < $n; ++$i) {
				$csim .= $this->y2texts[$i]->GetCSIMAreas();
			}
		}

		if ($this->yaxis != null && $this->xaxis != null) {
			$csim .= $this->yaxis->title->GetCSIMAreas();
			$csim .= $this->xaxis->title->GetCSIMAreas();
		}

		$n = safe_count($this->plots);
		for ($i = 0; $i < $n; ++$i) {
			$csim .= $this->plots[$i]->GetCSIMareas();
		}

		$n = safe_count($this->y2plots);
		for ($i = 0; $i < $n; ++$i) {
			$csim .= $this->y2plots[$i]->GetCSIMareas();
		}

		$n = safe_count($this->ynaxis);
		for ($i = 0; $i < $n; ++$i) {
			$m = safe_count($this->ynplots[$i]);
			for ($j = 0; $j < $m; ++$j) {
				$csim .= $this->ynplots[$i][$j]->GetCSIMareas();
			}
		}

		$n = safe_count($this->iTables);
		for ($i = 0; $i < $n; ++$i) {
			$csim .= $this->iTables[$i]->GetCSIMareas();
		}

		return $csim;
	}

	// Get a complete <MAP>..</MAP> tag for the final image map
	public function GetHTMLImageMap($aMapName){
		$im = "<map name=\"${aMapName}\" id=\"${aMapName}\" >\n";
		$im .= $this->GetCSIMareas();
		$im .= '</map>';

		return $im;
	}

	public function CheckCSIMCache($aCacheName, $aTimeOut = 60){
		global $_SERVER;

		if ($aCacheName == 'auto') {
			$aCacheName = basename($_SERVER['PHP_SELF']);
		}

		$urlarg				 = $this->GetURLArguments();
		$this->csimcachename	= CSIMCACHE_DIR . $aCacheName . $urlarg;
		$this->csimcachetimeout = $aTimeOut;

		// First determine if we need to check for a cached version
		// This differs from the standard cache in the sense that the
		// image and CSIM map HTML file is written relative to the directory
		// the script executes in and not the specified cache directory.
		// The reason for this is that the cache directory is not necessarily
		// accessible from the HTTP server.
		if ($this->csimcachename != '') {
			$dir	  = dirname($this->csimcachename);
			$base	 = basename($this->csimcachename);
			$base	 = strtok($base, '.');
			$suffix   = strtok('.');
			$basecsim = $dir . '/' . $base . '?' . $urlarg . '_csim_.html';
			$baseimg  = $dir . '/' . $base . '?' . $urlarg . '.' . $this->img->img_format;

			$timedout = false;
			// Does it exist at all ?

			if (file_exists($basecsim) && file_exists($baseimg)) {
				// Check that it hasn't timed out
				$diff = time() - filemtime($basecsim);
				if ($this->csimcachetimeout > 0 && ($diff > $this->csimcachetimeout * 60)) {
					$timedout = true;
					@unlink($basecsim);
					@unlink($baseimg);
				} else {
					if ($fh = @fopen($basecsim, 'r')) {
						fpassthru($fh);

						return true;
					}
					Util\JpGraphError::RaiseL(25027, $basecsim); //(" Can't open cached CSIM \"$basecsim\" for reading.");
				}
			}
		}

		return false;
	}

	// Build the argument string to be used with the csim images
	public static function GetURLArguments($aAddRecursiveBlocker = false){
		if ($aAddRecursiveBlocker) {
			// This is a JPGRAPH internal defined that prevents
			// us from recursively coming here again
			$urlarg = _CSIM_DISPLAY . '=1';
		}

		// Now reconstruct any user URL argument
		reset($_GET);
		foreach ($_GET as $key => $value) {
			if (is_array($value)) {
				foreach ($value as $k => $v) {
					$urlarg .= '&amp;' . $key . '%5B' . $k . '%5D=' . urlencode($v);
				}
			} else {
				$urlarg .= '&amp;' . $key . '=' . urlencode($value);
			}
		}

		// It's not ideal to convert POST argument to GET arguments
		// but there is little else we can do. One idea for the
		// future might be recreate the POST header in case.
		reset($_POST);
		foreach ($_POST as $key => $value) {
			if (is_array($value)) {
				foreach ($value as $k => $v) {
					$urlarg .= '&amp;' . $key . '%5B' . $k . '%5D=' . urlencode($v);
				}
			} else {
				$urlarg .= '&amp;' . $key . '=' . urlencode($value);
			}
		}

		return $urlarg;
	}

	public function SetCSIMImgAlt($aAlt){
		$this->iCSIMImgAlt = $aAlt;
	}

	public function StrokeCSIM($aScriptName = 'auto', $aCSIMName = '', $aBorder = 0){
		if ($aCSIMName == '') {
			// create a random map name
			srand((int) (microtime() * 1000000));
			$r		 = rand(0, 100000);
			$aCSIMName = '__mapname' . $r . '__';
		}

		if ($aScriptName == 'auto') {
			$aScriptName = basename($_SERVER['PHP_SELF']);
		}

		$urlarg = $this->GetURLArguments(true);

		if (empty($_GET[_CSIM_DISPLAY])) {
			// First determine if we need to check for a cached version
			// This differs from the standard cache in the sense that the
			// image and CSIM map HTML file is written relative to the directory
			// the script executes in and not the specified cache directory.
			// The reason for this is that the cache directory is not necessarily
			// accessible from the HTTP server.
			if ($this->csimcachename != '') {
				$dir	  = dirname($this->csimcachename);
				$base	 = basename($this->csimcachename);
				$base	 = strtok($base, '.');
				$suffix   = strtok('.');
				$basecsim = $dir . '/' . $base . '?' . $urlarg . '_csim_.html';
				$baseimg  = $base . '?' . $urlarg . '.' . $this->img->img_format;

				// Check that apache can write to directory specified

				if (file_exists($dir) && !is_writeable($dir)) {
					Util\JpGraphError::RaiseL(25028, $dir); //('Apache/PHP does not have permission to write to the CSIM cache directory ('.$dir.'). Check permissions.');
				}

				// Make sure directory exists
				$this->cache->MakeDirs($dir);

				// Write the image file
				$this->Stroke(CSIMCACHE_DIR . $baseimg);

				// Construct wrapper HTML and write to file and send it back to browser

				// In the src URL we must replace the '?' with its encoding to prevent the arguments
				// to be converted to real arguments.
				$tmp	  = str_replace('?', '%3f', $baseimg);
				$htmlwrap = $this->GetHTMLImageMap($aCSIMName) . "\n" .
				'<img src="' . CSIMCACHE_HTTP_DIR . $tmp . '" ismap="ismap" usemap="#' . $aCSIMName . ' width="' . $this->img->width . '" height="' . $this->img->height . '" alt="' . $this->iCSIMImgAlt . "\" />\n";

				if ($fh = @fopen($basecsim, 'w')) {
					fwrite($fh, $htmlwrap);
					fclose($fh);
					echo $htmlwrap;
				} else {
					Util\JpGraphError::RaiseL(25029, $basecsim); //(" Can't write CSIM \"$basecsim\" for writing. Check free space and permissions.");
				}
			} else {
				if ($aScriptName == '') {
					Util\JpGraphError::RaiseL(25030); //('Missing script name in call to StrokeCSIM(). You must specify the name of the actual image script as the first parameter to StrokeCSIM().');
				}
				echo $this->GetHTMLImageMap($aCSIMName) . $this->GetCSIMImgHTML($aCSIMName, $aScriptName, $aBorder);
			}
		} else {
			$this->Stroke();
		}
	}

	public function StrokeCSIMImage(){
		if (@$_GET[_CSIM_DISPLAY] == 1) {
			$this->Stroke();
		}
	}

	public function GetCSIMImgHTML($aCSIMName, $aScriptName = 'auto', $aBorder = 0){
		if ($aScriptName == 'auto') {
			$aScriptName = basename($_SERVER['PHP_SELF']);
		}
		$urlarg = $this->GetURLArguments(true);

		return '<img src="' . $aScriptName . '?' . $urlarg . '" ismap="ismap" usemap="#' . $aCSIMName . '" height="' . $this->img->height . '" alt="' . $this->iCSIMImgAlt . "\" />\n";
	}

	public function GetTextsYMinMax($aY2 = false){
		if ($aY2) {
			$txts = $this->y2texts;
		} else {
			$txts = $this->texts;
		}
		$n   = safe_count($txts);
		$min = null;
		$max = null;
		for ($i = 0; $i < $n; ++$i) {
			if ($txts[$i]->iScalePosY !== null && $txts[$i]->iScalePosX !== null) {
				if ($min === null) {
					$min = $max = $txts[$i]->iScalePosY;
				} else {
					$min = min($min, $txts[$i]->iScalePosY);
					$max = max($max, $txts[$i]->iScalePosY);
				}
			}
		}
		if ($min !== null) {
			return [$min, $max];
		}

		return null;
	}

	public function GetTextsXMinMax($aY2 = false){
		if ($aY2) {
			$txts = $this->y2texts;
		} else {
			$txts = $this->texts;
		}
		$n   = safe_count($txts);
		$min = null;
		$max = null;
		for ($i = 0; $i < $n; ++$i) {
			if ($txts[$i]->iScalePosY !== null && $txts[$i]->iScalePosX !== null) {
				if ($min === null) {
					$min = $max = $txts[$i]->iScalePosX;
				} else {
					$min = min($min, $txts[$i]->iScalePosX);
					$max = max($max, $txts[$i]->iScalePosX);
				}
			}
		}
		if ($min !== null) {
			return [$min, $max];
		}

		return null;
	}

	public function GetXMinMax(){
		list($min, $ymin) = $this->plots[0]->Min();
		list($max, $ymax) = $this->plots[0]->Max();

		$i = 0;
		// Some plots, e.g. PlotLine should not affect the scale
		// and will return (null,null). We should ignore those
		// values.
		while (($min === null || $max === null) && ($i < safe_count($this->plots) - 1)) {
			++$i;
			list($min, $ymin) = $this->plots[$i]->Min();
			list($max, $ymax) = $this->plots[$i]->Max();
		}

		foreach ($this->plots as $p) {
			list($xmin, $ymin) = $p->Min();
			list($xmax, $ymax) = $p->Max();

			if ($xmin !== null && $xmax !== null) {
				$min = min($xmin, $min);
				$max = max($xmax, $max);
			}
		}

		if ($this->y2axis != null) {
			foreach ($this->y2plots as $p) {
				list($xmin, $ymin) = $p->Min();
				list($xmax, $ymax) = $p->Max();
				$min			   = min($xmin, $min);
				$max			   = max($xmax, $max);
			}
		}

		$n = safe_count($this->ynaxis);
		for ($i = 0; $i < $n; ++$i) {
			if ($this->ynaxis[$i] != null) {
				foreach ($this->ynplots[$i] as $p) {
					list($xmin, $ymin) = $p->Min();
					list($xmax, $ymax) = $p->Max();
					$min			   = min($xmin, $min);
					$max			   = max($xmax, $max);
				}
			}
		}

		return [$min, $max];
	}

	public function AdjustMarginsForTitles(){
		$totrequired =
			($this->title->t != ''
			? $this->title->GetTextHeight($this->img) + $this->title->margin + 5 * SUPERSAMPLING_SCALE
			: 0) +
			($this->subtitle->t != ''
			? $this->subtitle->GetTextHeight($this->img) + $this->subtitle->margin + 5 * SUPERSAMPLING_SCALE
			: 0) +
			($this->subsubtitle->t != ''
			? $this->subsubtitle->GetTextHeight($this->img) + $this->subsubtitle->margin + 5 * SUPERSAMPLING_SCALE
			: 0);

		$btotrequired = 0;
		if ($this->xaxis != null && !$this->xaxis->hide && !$this->xaxis->hide_labels) {
			// Minimum bottom margin
			if ($this->xaxis->title->t != '') {
				if ($this->img->a == 90) {
					$btotrequired = $this->yaxis->title->GetTextHeight($this->img) + 7;
				} else {
					$btotrequired = $this->xaxis->title->GetTextHeight($this->img) + 7;
				}
			} else {
				$btotrequired = 0;
			}

			if ($this->img->a == 90) {
				$this->img->SetFont(
					$this->yaxis->font_family,
					$this->yaxis->font_style,
					$this->yaxis->font_size
				);
				$lh = $this->img->GetTextHeight('Mg', $this->yaxis->label_angle);
			} else {
				$this->img->SetFont(
					$this->xaxis->font_family,
					$this->xaxis->font_style,
					$this->xaxis->font_size
				);
				$lh = $this->img->GetTextHeight('Mg', $this->xaxis->label_angle);
			}

			$btotrequired += $lh + 6;
		}

		if ($this->img->a == 90) {
			// DO Nothing. It gets too messy to do this properly for 90 deg...
		} else {
			// need more top margin
			if ($this->img->top_margin < $totrequired) {
				$this->SetMargin(
					$this->img->raw_left_margin,
					$this->img->raw_right_margin,
					$totrequired / SUPERSAMPLING_SCALE,
					$this->img->raw_bottom_margin
				);
			}

			// need more bottom margin
			if ($this->img->bottom_margin < $btotrequired) {
				$this->SetMargin(
					$this->img->raw_left_margin,
					$this->img->raw_right_margin,
					$this->img->raw_top_margin,
					$btotrequired / SUPERSAMPLING_SCALE
				);
			}
		}
	}

	public function StrokeStore($aStrokeFileName){
		// Get the handler to prevent the library from sending the
		// image to the browser
		$ih = $this->Stroke(_IMG_HANDLER);

		// Stroke it to a file
		$this->img->Stream($aStrokeFileName);

		// Send it back to browser
		$this->img->Headers();
		$this->img->Stream();
	}

	public function doAutoscaleXAxis(){
		$aPlots = array_filter($this->plots, function ($plot) {
			//\Kint::dump($plot, $plot instanceof Plot\Plot);
			return $plot instanceof Plot\Plot;
		});

		//Check if we should autoscale x-axis
		if (!$this->xscale->IsSpecified()) {
			if (substr($this->axtype, 0, 4) == 'text') {
				$max = 0;
				$n   = safe_count($aPlots);

				for ($i = 0; $i < $n; ++$i) {
					$p = $aPlots[$i];

					// We need some unfortunate sub class knowledge here in order
					// to increase number of data points in case it is a line plot
					// which has the barcenter set. If not it could mean that the
					// last point of the data is outside the scale since the barcenter
					// settings means that we will shift the entire plot half a tick step
					// to the right in oder to align with the center of the bars.

					$cl = strtolower(get_class($p));
					if (empty($p->barcenter)) {
						$max = max($max, $p->numpoints - 1);
					} else {
						$max = max($max, $p->numpoints);
					}
				}
				$min = 0;
				if ($this->y2axis != null) {
					foreach ($this->y2plots as $p) {
						$max = max($max, $p->numpoints - 1);
					}
				}
				$n = safe_count($this->ynaxis);
				for ($i = 0; $i < $n; ++$i) {
					if ($this->ynaxis[$i] != null) {
						foreach ($this->ynplots[$i] as $p) {
							$max = max($max, $p->numpoints - 1);
						}
					}
				}

				$this->xscale->Update($this->img, $min, $max);
				$this->xscale->ticks->Set($this->xaxis->tick_step, 1);
				$this->xscale->ticks->SupressMinorTickMarks();
			} else {
				list($min, $max) = $this->GetXMinMax();

				$lres = $this->GetLinesXMinMax($this->lines);
				if ($lres) {
					list($linmin, $linmax) = $lres;
					$min				   = min($min, $linmin);
					$max				   = max($max, $linmax);
				}

				$lres = $this->GetLinesXMinMax($this->y2lines);
				if ($lres) {
					list($linmin, $linmax) = $lres;
					$min				   = min($min, $linmin);
					$max				   = max($max, $linmax);
				}

				$tres = $this->GetTextsXMinMax();
				if ($tres) {
					list($tmin, $tmax) = $tres;
					$min			   = min($min, $tmin);
					$max			   = max($max, $tmax);
				}

				$tres = $this->GetTextsXMinMax(true);
				if ($tres) {
					list($tmin, $tmax) = $tres;
					$min			   = min($min, $tmin);
					$max			   = max($max, $tmax);
				}

				$this->xscale->AutoScale($this->img, $min, $max, round($this->img->plotwidth / $this->xtick_factor));
			}

			//Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
			if (!is_numeric($this->yaxis->pos) && !is_string($this->yaxis->pos)) {
				$this->yaxis->SetPos($this->xscale->GetMinVal());
			}
		} elseif ($this->xscale->IsSpecified() &&
			($this->xscale->auto_ticks || !$this->xscale->ticks->IsSpecified())) {
			// The tick calculation will use the user suplied min/max values to determine
			// the ticks. If auto_ticks is false the exact user specifed min and max
			// values will be used for the scale.
			// If auto_ticks is true then the scale might be slightly adjusted
			// so that the min and max values falls on an even major step.
			$min = $this->xscale->scale[0];
			$max = $this->xscale->scale[1];
			$this->xscale->AutoScale($this->img, $min, $max, round($this->img->plotwidth / $this->xtick_factor), false);

			// Now make sure we show enough precision to accurate display the
			// labels. If this is not done then the user might end up with
			// a scale that might actually start with, say 13.5, butdue to rounding
			// the scale label will ony show 14.
			if (abs(floor($min) - $min) > 0) {
				// If the user has set a format then we bail out
				if ($this->xscale->ticks->label_formatstr == '' && $this->xscale->ticks->label_dateformatstr == '') {
					$this->xscale->ticks->precision = abs(floor(log10(abs(floor($min) - $min)))) + 1;
				}
			}
		}

		// Position the optional Y2 and Yn axis to the rightmost position of the x-axis
		if ($this->y2axis != null) {
			if (!is_numeric($this->y2axis->pos) && !is_string($this->y2axis->pos)) {
				$this->y2axis->SetPos($this->xscale->GetMaxVal());
			}
			$this->y2axis->SetTitleSide(SIDE_RIGHT);
		}

		$n	  = safe_count($this->ynaxis);
		$nY2adj = $this->y2axis != null ? $this->iYAxisDeltaPos : 0;
		for ($i = 0; $i < $n; ++$i) {
			if ($this->ynaxis[$i] != null) {
				if (!is_numeric($this->ynaxis[$i]->pos) && !is_string($this->ynaxis[$i]->pos)) {
					$this->ynaxis[$i]->SetPos($this->xscale->GetMaxVal());
					$this->ynaxis[$i]->SetPosAbsDelta($i * $this->iYAxisDeltaPos + $nY2adj);
				}
				$this->ynaxis[$i]->SetTitleSide(SIDE_RIGHT);
			}
		}
	}

	public function doAutoScaleYnAxis(){
		if ($this->y2scale != null) {
			if (!$this->y2scale->IsSpecified() && safe_count($this->y2plots) > 0) {
				list($min, $max) = $this->GetPlotsYMinMax($this->y2plots);

				$lres = $this->GetLinesYMinMax($this->y2lines);
				if (is_array($lres)) {
					list($linmin, $linmax) = $lres;
					$min				   = min($min, $linmin);
					$max				   = max($max, $linmax);
				}
				$tres = $this->GetTextsYMinMax(true);
				if (is_array($tres)) {
					list($tmin, $tmax) = $tres;
					$min			   = min($min, $tmin);
					$max			   = max($max, $tmax);
				}
				$this->y2scale->AutoScale($this->img, $min, $max, $this->img->plotheight / $this->ytick_factor);
			} elseif ($this->y2scale->IsSpecified() && ($this->y2scale->auto_ticks || !$this->y2scale->ticks->IsSpecified())) {
				// The tick calculation will use the user suplied min/max values to determine
				// the ticks. If auto_ticks is false the exact user specifed min and max
				// values will be used for the scale.
				// If auto_ticks is true then the scale might be slightly adjusted
				// so that the min and max values falls on an even major step.
				$min = $this->y2scale->scale[0];
				$max = $this->y2scale->scale[1];
				$this->y2scale->AutoScale(
					$this->img,
					$min,
					$max,
					$this->img->plotheight / $this->ytick_factor,
					$this->y2scale->auto_ticks
				);

				// Now make sure we show enough precision to accurate display the
				// labels. If this is not done then the user might end up with
				// a scale that might actually start with, say 13.5, butdue to rounding
				// the scale label will ony show 14.
				if (abs(floor($min) - $min) > 0) {
					// If the user has set a format then we bail out
					if ($this->y2scale->ticks->label_formatstr == '' && $this->y2scale->ticks->label_dateformatstr == '') {
						$this->y2scale->ticks->precision = abs(floor(log10(abs(floor($min) - $min)))) + 1;
					}
				}
			}
		}

		//
		// Autoscale the extra Y-axises
		//
		$n = safe_count($this->ynaxis);
		for ($i = 0; $i < $n; ++$i) {
			if ($this->ynscale[$i] != null) {
				if (!$this->ynscale[$i]->IsSpecified() && safe_count($this->ynplots[$i]) > 0) {
					list($min, $max) = $this->GetPlotsYMinMax($this->ynplots[$i]);
					$this->ynscale[$i]->AutoScale($this->img, $min, $max, $this->img->plotheight / $this->ytick_factor);
				} elseif ($this->ynscale[$i]->IsSpecified() && ($this->ynscale[$i]->auto_ticks || !$this->ynscale[$i]->ticks->IsSpecified())) {
					// The tick calculation will use the user suplied min/max values to determine
					// the ticks. If auto_ticks is false the exact user specifed min and max
					// values will be used for the scale.
					// If auto_ticks is true then the scale might be slightly adjusted
					// so that the min and max values falls on an even major step.
					$min = $this->ynscale[$i]->scale[0];
					$max = $this->ynscale[$i]->scale[1];
					$this->ynscale[$i]->AutoScale(
						$this->img,
						$min,
						$max,
						$this->img->plotheight / $this->ytick_factor,
						$this->ynscale[$i]->auto_ticks
					);

					// Now make sure we show enough precision to accurate display the
					// labels. If this is not done then the user might end up with
					// a scale that might actually start with, say 13.5, butdue to rounding
					// the scale label will ony show 14.
					if (abs(floor($min) - $min) > 0) {
						// If the user has set a format then we bail out
						if ($this->ynscale[$i]->ticks->label_formatstr == '' && $this->ynscale[$i]->ticks->label_dateformatstr == '') {
							$this->ynscale[$i]->ticks->precision = abs(floor(log10(abs(floor($min) - $min)))) + 1;
						}
					}
				}
			}
		}
	}

	public function doAutoScaleYAxis(){
		//Check if we should autoscale y-axis
		if (!$this->yscale->IsSpecified() && safe_count($this->plots) > 0) {
			list($min, $max) = $this->GetPlotsYMinMax($this->plots);
			$lres			= $this->GetLinesYMinMax($this->lines);
			if (is_array($lres)) {
				list($linmin, $linmax) = $lres;
				$min				   = min($min, $linmin);
				$max				   = max($max, $linmax);
			}
			$tres = $this->GetTextsYMinMax();
			if (is_array($tres)) {
				list($tmin, $tmax) = $tres;
				$min			   = min($min, $tmin);
				$max			   = max($max, $tmax);
			}
			$this->yscale->AutoScale(
				$this->img,
				$min,
				$max,
				$this->img->plotheight / $this->ytick_factor
			);
		} elseif ($this->yscale->IsSpecified() && ($this->yscale->auto_ticks || !$this->yscale->ticks->IsSpecified())) {
			// The tick calculation will use the user suplied min/max values to determine
			// the ticks. If auto_ticks is false the exact user specifed min and max
			// values will be used for the scale.
			// If auto_ticks is true then the scale might be slightly adjusted
			// so that the min and max values falls on an even major step.
			$min = $this->yscale->scale[0];
			$max = $this->yscale->scale[1];
			$this->yscale->AutoScale(
				$this->img,
				$min,
				$max,
				$this->img->plotheight / $this->ytick_factor,
				$this->yscale->auto_ticks
			);

			// Now make sure we show enough precision to accurate display the
			// labels. If this is not done then the user might end up with
			// a scale that might actually start with, say 13.5, butdue to rounding
			// the scale label will ony show 14.
			if (abs(floor($min) - $min) > 0) {
				// If the user has set a format then we bail out
				if ($this->yscale->ticks->label_formatstr == '' && $this->yscale->ticks->label_dateformatstr == '') {
					$this->yscale->ticks->precision = abs(floor(log10(abs(floor($min) - $min)))) + 1;
				}
			}
		}
	}

	public function InitScaleConstants(){
		// Setup scale constants
		if ($this->yscale) {
			$this->yscale->InitConstants($this->img);
		}

		if ($this->xscale) {
			$this->xscale->InitConstants($this->img);
		}

		if ($this->y2scale) {
			$this->y2scale->InitConstants($this->img);
		}

		$n = safe_count($this->ynscale);
		for ($i = 0; $i < $n; ++$i) {
			if ($this->ynscale[$i]) {
				$this->ynscale[$i]->InitConstants($this->img);
			}
		}
	}

	public function doPrestrokeAdjustments(){
		// Do any pre-stroke adjustment that is needed by the different plot types
		// (i.e bar plots want's to add an offset to the x-labels etc)
		for ($i = 0; $i < safe_count($this->plots); ++$i) {
			if ($this->plots[$i] instanceof Plot\Plot) {
				$this->plots[$i]->PreStrokeAdjust($this);
				$this->plots[$i]->DoLegend($this);
			}
		}

		// Any plots on the second Y scale?
		if ($this->y2scale != null) {
			for ($i = 0; $i < safe_count($this->y2plots); ++$i) {
				if ($this->plots[$i] instanceof Plot\Plot) {
					$this->y2plots[$i]->PreStrokeAdjust($this);
					$this->y2plots[$i]->DoLegend($this);
				}
			}
		}

		// Any plots on the extra Y axises?
		$n = safe_count($this->ynaxis);
		for ($i = 0; $i < $n; ++$i) {
			if ($this->ynplots == null || $this->ynplots[$i] == null) {
				Util\JpGraphError::RaiseL(25032, $i); //("No plots for Y-axis nbr:$i");
			}
			$m = safe_count($this->ynplots[$i]);
			for ($j = 0; $j < $m; ++$j) {
				$this->ynplots[$i][$j]->PreStrokeAdjust($this);
				$this->ynplots[$i][$j]->DoLegend($this);
			}
		}
	}

	public function StrokeBands($aDepth, $aCSIM){
		// Stroke bands
		if ($this->bands != null && !$aCSIM) {
			for ($i = 0; $i < safe_count($this->bands); ++$i) {
				// Stroke all bands that asks to be in the background
				if ($this->bands[$i]->depth == $aDepth) {
					$this->bands[$i]->Stroke($this->img, $this->xscale, $this->yscale);
				}
			}
		}

		if ($this->y2bands != null && $this->y2scale != null && !$aCSIM) {
			for ($i = 0; $i < safe_count($this->y2bands); ++$i) {
				// Stroke all bands that asks to be in the foreground
				if ($this->y2bands[$i]->depth == $aDepth) {
					$this->y2bands[$i]->Stroke($this->img, $this->xscale, $this->y2scale);
				}
			}
		}
	}

	// Stroke the graph
	// $aStrokeFileName If != "" the image will be written to this file and NOT
	// streamed back to the browser
	public function Stroke($aStrokeFileName = ''){
		// Fist make a sanity check that user has specified a scale
		if (empty($this->yscale)) {
			Util\JpGraphError::RaiseL(25031); //('You must specify what scale to use with a call to Graph::SetScale().');
		}

		// Start by adjusting the margin so that potential titles will fit.
		$this->AdjustMarginsForTitles();

		// Give the plot a chance to do any scale adjuments the individual plots
		// wants to do. Right now this is only used by the contour plot to set scale
		// limits
		for ($i = 0; $i < safe_count($this->plots); ++$i) {
			if ($this->plots[$i] instanceof Plot\Plot) {
				$this->plots[$i]->PreScaleSetup($this);
			}
			//\Kint::dump($this->plots[$i]);
		}

		// Init scale constants that are used to calculate the transformation from
		// world to pixel coordinates
		$this->InitScaleConstants();

		// If the filename is the predefined value = '_csim_special_'
		// we assume that the call to stroke only needs to do enough
		// to correctly generate the CSIM maps.
		// We use this variable to skip things we don't strictly need
		// to do to generate the image map to improve performance
		// a best we can. Therefor you will see a lot of tests !$_csim in the
		// code below.
		$_csim = ($aStrokeFileName === _CSIM_SPECIALFILE);

		// If we are called the second time (perhaps the user has called GetHTMLImageMap()
		// himself then the legends have alsready been populated once in order to get the
		// CSIM coordinats. Since we do not want the legends to be populated a second time
		// we clear the legends
		$this->legend->Clear();

		// We need to know if we have stroked the plot in the
		// GetCSIMareas. Otherwise the CSIM hasn't been generated
		// and in the case of GetCSIM called before stroke to generate
		// CSIM without storing an image to disk GetCSIM must call Stroke.
		$this->iHasStroked = true;

		// Setup pre-stroked adjustments and Legends
		$this->doPrestrokeAdjustments();

		if ($this->graph_theme) {
			$this->graph_theme->PreStrokeApply($this);
		}

		// Bail out if any of the Y-axis not been specified and
		// has no plots. (This means it is impossible to do autoscaling and
		// no other scale was given so we can't possible draw anything). If you use manual
		// scaling you also have to supply the tick steps as well.
		if ((!$this->yscale->IsSpecified() && safe_count($this->plots) == 0) ||
			($this->y2scale != null && !$this->y2scale->IsSpecified() && safe_count($this->y2plots) == 0)) {
			//$e = "n=". safe_count($this->y2plots)."\n";
			// $e = "Can't draw unspecified Y-scale.<br>\nYou have either:<br>\n";
			// $e .= "1. Specified an Y axis for autoscaling but have not supplied any plots<br>\n";
			// $e .= "2. Specified a scale manually but have forgot to specify the tick steps";
			Util\JpGraphError::RaiseL(25026);
		}

		// Bail out if no plots and no specified X-scale
		if ((!$this->xscale->IsSpecified() && safe_count($this->plots) == 0 && safe_count($this->y2plots) == 0)) {
			Util\JpGraphError::RaiseL(25034); //("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
		}

		// Autoscale the normal Y-axis
		$this->doAutoScaleYAxis();

		// Autoscale all additiopnal y-axis
		$this->doAutoScaleYnAxis();

		// Autoscale the regular x-axis and position the y-axis properly
		$this->doAutoScaleXAxis();

		// If we have a negative values and x-axis position is at 0
		// we need to supress the first and possible the last tick since
		// they will be drawn on top of the y-axis (and possible y2 axis)
		// The test below might seem strange the reasone being that if
		// the user hasn't specified a value for position this will not
		// be set until we do the stroke for the axis so as of now it
		// is undefined.
		// For X-text scale we ignore all this since the tick are usually
		// much further in and not close to the Y-axis. Hence the test
		// for 'text'
		if (($this->yaxis->pos == $this->xscale->GetMinVal() || (is_string($this->yaxis->pos) && $this->yaxis->pos == 'min')) &&
			!is_numeric($this->xaxis->pos) && $this->yscale->GetMinVal() < 0 &&
			substr($this->axtype, 0, 4) != 'text' && $this->xaxis->pos != 'min') {
			//$this->yscale->ticks->SupressZeroLabel(false);
			$this->xscale->ticks->SupressFirst();
			if ($this->y2axis != null) {
				$this->xscale->ticks->SupressLast();
			}
		} elseif (!is_numeric($this->yaxis->pos) && $this->yaxis->pos == 'max') {
			$this->xscale->ticks->SupressLast();
		}

		if (!$_csim) {
			$this->StrokePlotArea();
			if ($this->iIconDepth == DEPTH_BACK) {
				$this->StrokeIcons();
			}
		}
		$this->StrokeAxis(false);

		// Stroke colored bands
		$this->StrokeBands(DEPTH_BACK, $_csim);

		if ($this->grid_depth == DEPTH_BACK && !$_csim) {
			$this->ygrid->Stroke();
			$this->xgrid->Stroke();
		}

		// Stroke Y2-axis
		if ($this->y2axis != null && !$_csim) {
			$this->y2axis->Stroke($this->xscale);
			$this->y2grid->Stroke();
		}

		// Stroke yn-axis
		$n = safe_count($this->ynaxis);
		for ($i = 0; $i < $n; ++$i) {
			$this->ynaxis[$i]->Stroke($this->xscale);
		}

		$oldoff = $this->xscale->off;
		if (substr($this->axtype, 0, 4) == 'text') {
			if ($this->text_scale_abscenteroff > -1) {
				// For a text scale the scale factor is the number of pixel per step.
				// Hence we can use the scale factor as a substitute for number of pixels
				// per major scale step and use that in order to adjust the offset so that
				// an object of width "abscenteroff" becomes centered.
				$this->xscale->off += round($this->xscale->scale_factor / 2) - round($this->text_scale_abscenteroff / 2);
			} else {
				$this->xscale->off += ceil($this->xscale->scale_factor * $this->text_scale_off * $this->xscale->ticks->minor_step);
			}
		}

		if ($this->iDoClipping) {
			$oldimage = $this->img->CloneCanvasH();
		}

		if (!$this->y2orderback) {
			// Stroke all plots for Y1 axis
			for ($i = 0; $i < safe_count($this->plots); ++$i) {
				$this->plots[$i]->Stroke($this->img, $this->xscale, $this->yscale);
				if ($this->plots[$i] instanceof Plot\Plot) {
					$this->plots[$i]->StrokeMargin($this->img);
				}
			}
		}

		// Stroke all plots for Y2 axis
		if ($this->y2scale != null) {
			for ($i = 0; $i < safe_count($this->y2plots); ++$i) {
				$this->y2plots[$i]->Stroke($this->img, $this->xscale, $this->y2scale);
			}
		}

		if ($this->y2orderback) {
			// Stroke all plots for Y1 axis
			for ($i = 0; $i < safe_count($this->plots); ++$i) {
				$this->plots[$i]->Stroke($this->img, $this->xscale, $this->yscale);
				if ($this->plots[$i] instanceof Plot\Plot) {
					$this->plots[$i]->StrokeMargin($this->img);
				}
			}
		}

		$n = safe_count($this->ynaxis);
		for ($i = 0; $i < $n; ++$i) {
			$m = safe_count($this->ynplots[$i]);
			for ($j = 0; $j < $m; ++$j) {
				$this->ynplots[$i][$j]->Stroke($this->img, $this->xscale, $this->ynscale[$i]);
				if ($this->ynplots[$i][$j] instanceof Plot\Plot) {
					$this->ynplots[$i][$j]->StrokeMargin($this->img);
				}
			}
		}

		if ($this->iIconDepth == DEPTH_FRONT) {
			$this->StrokeIcons();
		}

		if ($this->iDoClipping) {
			// Clipping only supports graphs at 0 and 90 degrees
			if ($this->img->a == 0) {
				$this->img->CopyCanvasH(
					$oldimage,
					$this->img->img,
					$this->img->left_margin,
					$this->img->top_margin,
					$this->img->left_margin,
					$this->img->top_margin,
					$this->img->plotwidth + 1,
					$this->img->plotheight
				);
			} elseif ($this->img->a == 90) {
				$adj = ($this->img->height - $this->img->width) / 2;
				$this->img->CopyCanvasH(
					$oldimage,
					$this->img->img,
					$this->img->bottom_margin - $adj,
					$this->img->left_margin + $adj,
					$this->img->bottom_margin - $adj,
					$this->img->left_margin + $adj,
					$this->img->plotheight + 1,
					$this->img->plotwidth
				);
			} else {
				Util\JpGraphError::RaiseL(25035, $this->img->a); //('You have enabled clipping. Cliping is only supported for graphs at 0 or 90 degrees rotation. Please adjust you current angle (='.$this->img->a.' degrees) or disable clipping.');
			}
			$this->img->Destroy();
			$this->img->SetCanvasH($oldimage);
		}

		$this->xscale->off = $oldoff;

		if ($this->grid_depth == DEPTH_FRONT && !$_csim) {
			$this->ygrid->Stroke();
			$this->xgrid->Stroke();
		}

		// Stroke colored bands
		$this->StrokeBands(DEPTH_FRONT, $_csim);

		// Finally draw the axis again since some plots may have nagged
		// the axis in the edges.
		if (!$_csim) {
			$this->StrokeAxis();
		}

		if ($this->y2scale != null && !$_csim) {
			$this->y2axis->Stroke($this->xscale, false);
		}

		if (!$_csim) {
			$this->StrokePlotBox();
		}

		// The titles and legends never gets rotated so make sure
		// that the angle is 0 before stroking them
		$aa = $this->img->SetAngle(0);
		$this->StrokeTitles();
		$this->footer->Stroke($this->img);
		$this->legend->Stroke($this->img);
		$this->img->SetAngle($aa);
		$this->StrokeTexts();
		$this->StrokeTables();

		if (!$_csim) {
			$this->img->SetAngle($aa);

			// Draw an outline around the image map
			if (_JPG_DEBUG) {
				$this->DisplayClientSideaImageMapAreas();
			}

			// Should we do any final image transformation
			if ($this->iImgTrans) {
				$tform		  = new Image\ImgTrans($this->img->img);
				$this->img->img = $tform->Skew3D(
					$this->iImgTransHorizon,
					$this->iImgTransSkewDist,
					$this->iImgTransDirection,
					$this->iImgTransHighQ,
					$this->iImgTransMinSize,
					$this->iImgTransFillColor,
					$this->iImgTransBorder
				);
			}

			// If the filename is given as the special "__handle"
			// then the image handler is returned and the image is NOT
			// streamed back
			if ($aStrokeFileName == _IMG_HANDLER) {
				return $this->img->img;
			}
			// Finally stream the generated picture
			$this->cache->PutAndStream($this->img, $this->cache_name, $this->inline, $aStrokeFileName);
		}
	}

	public function SetAxisLabelBackground($aType, $aXFColor = 'lightgray', $aXColor = 'black', $aYFColor = 'lightgray', $aYColor = 'black'){
		$this->iAxisLblBgType	   = $aType;
		$this->iXAxisLblBgFillColor = $aXFColor;
		$this->iXAxisLblBgColor	 = $aXColor;
		$this->iYAxisLblBgFillColor = $aYFColor;
		$this->iYAxisLblBgColor	 = $aYColor;
	}

	public function StrokeAxisLabelBackground(){
		// Types
		// 0 = No background
		// 1 = Only X-labels, length of axis
		// 2 = Only Y-labels, length of axis
		// 3 = As 1 but extends to width of graph
		// 4 = As 2 but extends to height of graph
		// 5 = Combination of 3 & 4
		// 6 = Combination of 1 & 2

		$t = $this->iAxisLblBgType;
		if ($t < 1) {
			return;
		}

		// Stroke optional X-axis label background color
		if ($t == 1 || $t == 3 || $t == 5 || $t == 6) {
			$this->img->PushColor($this->iXAxisLblBgFillColor);
			if ($t == 1 || $t == 6) {
				$xl = $this->img->left_margin;
				$yu = $this->img->height - $this->img->bottom_margin + 1;
				$xr = $this->img->width - $this->img->right_margin;
				$yl = $this->img->height - 1 - $this->frame_weight;
			} else {
				// t==3 || t==5
				$xl = $this->frame_weight;
				$yu = $this->img->height - $this->img->bottom_margin + 1;
				$xr = $this->img->width - 1 - $this->frame_weight;
				$yl = $this->img->height - 1 - $this->frame_weight;
			}

			$this->img->FilledRectangle($xl, $yu, $xr, $yl);
			$this->img->PopColor();

			// Check if we should add the vertical lines at left and right edge
			if ($this->iXAxisLblBgColor !== '') {
				// Hardcode to one pixel wide
				$this->img->SetLineWeight(1);
				$this->img->PushColor($this->iXAxisLblBgColor);
				if ($t == 1 || $t == 6) {
					$this->img->Line($xl, $yu, $xl, $yl);
					$this->img->Line($xr, $yu, $xr, $yl);
				} else {
					$xl = $this->img->width - $this->img->right_margin;
					$this->img->Line($xl, $yu - 1, $xr, $yu - 1);
				}
				$this->img->PopColor();
			}
		}

		if ($t == 2 || $t == 4 || $t == 5 || $t == 6) {
			$this->img->PushColor($this->iYAxisLblBgFillColor);
			if ($t == 2 || $t == 6) {
				$xl = $this->frame_weight;
				$yu = $this->frame_weight + $this->img->top_margin;
				$xr = $this->img->left_margin - 1;
				$yl = $this->img->height - $this->img->bottom_margin + 1;
			} else {
				$xl = $this->frame_weight;
				$yu = $this->frame_weight;
				$xr = $this->img->left_margin - 1;
				$yl = $this->img->height - 1 - $this->frame_weight;
			}

			$this->img->FilledRectangle($xl, $yu, $xr, $yl);
			$this->img->PopColor();

			// Check if we should add the vertical lines at left and right edge
			if ($this->iXAxisLblBgColor !== '') {
				$this->img->PushColor($this->iXAxisLblBgColor);
				if ($t == 2 || $t == 6) {
					$this->img->Line($xl, $yu - 1, $xr, $yu - 1);
					$this->img->Line($xl, $yl - 1, $xr, $yl - 1);
				} else {
					$this->img->Line($xr + 1, $yu, $xr + 1, $this->img->top_margin);
				}
				$this->img->PopColor();
			}
		}
	}

	public function StrokeAxis($aStrokeLabels = true){
		if ($aStrokeLabels) {
			$this->StrokeAxisLabelBackground();
		}

		// Stroke axis
		if ($this->iAxisStyle != AXSTYLE_SIMPLE) {
			switch ($this->iAxisStyle) {
				case AXSTYLE_BOXIN:
					$toppos	= SIDE_DOWN;
					$bottompos = SIDE_UP;
					$leftpos   = SIDE_RIGHT;
					$rightpos  = SIDE_LEFT;

					break;
				case AXSTYLE_BOXOUT:
					$toppos	= SIDE_UP;
					$bottompos = SIDE_DOWN;
					$leftpos   = SIDE_LEFT;
					$rightpos  = SIDE_RIGHT;

					break;
				case AXSTYLE_YBOXIN:
					$toppos	= false;
					$bottompos = SIDE_UP;
					$leftpos   = SIDE_RIGHT;
					$rightpos  = SIDE_LEFT;

					break;
				case AXSTYLE_YBOXOUT:
					$toppos	= false;
					$bottompos = SIDE_DOWN;
					$leftpos   = SIDE_LEFT;
					$rightpos  = SIDE_RIGHT;

					break;
				default:
					Util\JpGraphError::RaiseL(25036, $this->iAxisStyle); //('Unknown AxisStyle() : '.$this->iAxisStyle);
					break;
			}

			// By default we hide the first label so it doesn't cross the
			// Y-axis in case the positon hasn't been set by the user.
			// However, if we use a box we always want the first value
			// displayed so we make sure it will be displayed.
			$this->xscale->ticks->SupressFirst(false);

			// Now draw the bottom X-axis
			$this->xaxis->SetPos('min');
			$this->xaxis->SetLabelSide(SIDE_DOWN);
			$this->xaxis->scale->ticks->SetSide($bottompos);
			$this->xaxis->Stroke($this->yscale, $aStrokeLabels);

			if ($toppos !== false) {
				// We also want a top X-axis
				$this->xaxis = $this->xaxis;
				$this->xaxis->SetPos('max');
				$this->xaxis->SetLabelSide(SIDE_UP);
				// No title for the top X-axis
				if ($aStrokeLabels) {
					$this->xaxis->title->Set('');
				}
				$this->xaxis->scale->ticks->SetSide($toppos);
				$this->xaxis->Stroke($this->yscale, $aStrokeLabels);
			}

			// Stroke the left Y-axis
			$this->yaxis->SetPos('min');
			$this->yaxis->SetLabelSide(SIDE_LEFT);
			$this->yaxis->scale->ticks->SetSide($leftpos);
			$this->yaxis->Stroke($this->xscale, $aStrokeLabels);

			// Stroke the  right Y-axis
			$this->yaxis->SetPos('max');
			// No title for the right side
			if ($aStrokeLabels) {
				$this->yaxis->title->Set('');
			}
			$this->yaxis->SetLabelSide(SIDE_RIGHT);
			$this->yaxis->scale->ticks->SetSide($rightpos);
			$this->yaxis->Stroke($this->xscale, $aStrokeLabels);
		} else {
			$this->xaxis->Stroke($this->yscale, $aStrokeLabels);
			$this->yaxis->Stroke($this->xscale, $aStrokeLabels);
		}
	}

	// Private helper function for backgound image
	public static function LoadBkgImage($aImgFormat = '', $aFile = '', $aImgStr = ''){
		if ($aImgStr != '') {
			return Image::CreateFromString($aImgStr);
		}

		// Remove case sensitivity and setup appropriate function to create image
		// Get file extension. This should be the LAST '.' separated part of the filename
		$e   = explode('.', $aFile);
		$ext = strtolower($e[count($e) - 1]);
		if ($ext == 'jpeg') {
			$ext = 'jpg';
		}

		if (trim($ext) == '') {
			$ext = 'png'; // Assume PNG if no extension specified
		}

		if ($aImgFormat == '') {
			$imgtag = $ext;
		} else {
			$imgtag = $aImgFormat;
		}

		$supported = imagetypes();
		if (($ext == 'jpg' && !($supported & IMG_JPG)) ||
			($ext == 'gif' && !($supported & IMG_GIF)) ||
			($ext == 'png' && !($supported & IMG_PNG)) ||
			($ext == 'bmp' && !($supported & IMG_WBMP)) ||
			($ext == 'xpm' && !($supported & IMG_XPM))) {
			Util\JpGraphError::RaiseL(25037, $aFile); //('The image format of your background image ('.$aFile.') is not supported in your system configuration. ');
		}

		if ($imgtag == 'jpg' || $imgtag == 'jpeg') {
			$f	  = 'imagecreatefromjpeg';
			$imgtag = 'jpg';
		} else {
			$f = 'imagecreatefrom' . $imgtag;
		}

		// Compare specified image type and file extension
		if ($imgtag != $ext) {
			//$t = "Background image seems to be of different type (has different file extension) than specified imagetype. Specified: '".$aImgFormat."'File: '".$aFile."'";
			Util\JpGraphError::RaiseL(25038, $aImgFormat, $aFile);
		}

		$img = @$f($aFile);
		if (!$img) {
			Util\JpGraphError::RaiseL(25039, $aFile); //(" Can't read background image: '".$aFile."'");
		}

		return $img;
	}

	public function StrokePlotGrad(){
		if ($this->plot_gradtype < 0) {
			return;
		}

		$grad = new Plot\Gradient($this->img);
		$xl   = $this->img->left_margin;
		$yt   = $this->img->top_margin;
		$xr   = $xl + $this->img->plotwidth + 1;
		$yb   = $yt + $this->img->plotheight;
		$grad->FilledRectangle($xl, $yt, $xr, $yb, $this->plot_gradfrom, $this->plot_gradto, $this->plot_gradtype);
	}

	public function StrokeBackgroundGrad(){
		if ($this->bkg_gradtype < 0) {
			return;
		}

		$grad = new Plot\Gradient($this->img);
		if ($this->bkg_gradstyle == BGRAD_PLOT) {
			$xl = $this->img->left_margin;
			$yt = $this->img->top_margin;
			$xr = $xl + $this->img->plotwidth + 1;
			$yb = $yt + $this->img->plotheight;
			$grad->FilledRectangle($xl, $yt, $xr, $yb, $this->bkg_gradfrom, $this->bkg_gradto, $this->bkg_gradtype);
		} else {
			$xl = 0;
			$yt = 0;
			$xr = $xl + $this->img->width - 1;
			$yb = $yt + $this->img->height - 1;
			if ($this->doshadow) {
				$xr -= $this->shadow_width;
				$yb -= $this->shadow_width;
			}
			if ($this->doframe) {
				$yt += $this->frame_weight;
				$yb -= $this->frame_weight;
				$xl += $this->frame_weight;
				$xr -= $this->frame_weight;
			}
			$aa = $this->img->SetAngle(0);
			$grad->FilledRectangle($xl, $yt, $xr, $yb, $this->bkg_gradfrom, $this->bkg_gradto, $this->bkg_gradtype);
			$aa = $this->img->SetAngle($aa);
		}
	}

	public function StrokeFrameBackground(){
		if ($this->background_image != '' && $this->background_cflag != '') {
			Util\JpGraphError::RaiseL(25040); //('It is not possible to specify both a background image and a background country flag.');
		}
		if ($this->background_image != '') {
			$bkgimg = $this->LoadBkgImage($this->background_image_format, $this->background_image);
		} elseif ($this->background_cflag != '') {
			$fobj						= new Image\FlagImages(FLAGSIZE4);
			$dummy					   = '';
			$bkgimg					  = $fobj->GetImgByName($this->background_cflag, $dummy);
			$this->background_image_mix  = $this->background_cflag_mix;
			$this->background_image_type = $this->background_cflag_type;
		} else {
			return;
		}

		$bw = imagesx($bkgimg);
		$bh = imagesy($bkgimg);

		// No matter what the angle is we always stroke the image and frame
		// assuming it is 0 degree
		$aa = $this->img->SetAngle(0);

		switch ($this->background_image_type) {
			case BGIMG_FILLPLOT: // Resize to just fill the plotarea
				$this->FillMarginArea();
				$this->StrokeFrame();
				// Special case to hande 90 degree rotated graph corectly
				if ($aa == 90) {
					$this->img->SetAngle(90);
					$this->FillPlotArea();
					$aa  = $this->img->SetAngle(0);
					$adj = ($this->img->height - $this->img->width) / 2;
					$this->img->CopyMerge(
						$bkgimg,
						$this->img->bottom_margin - $adj,
						$this->img->left_margin + $adj,
						0,
						0,
						$this->img->plotheight + 1,
						$this->img->plotwidth,
						$bw,
						$bh,
						$this->background_image_mix
					);
				} else {
					$this->FillPlotArea();
					$this->img->CopyMerge(
						$bkgimg,
						$this->img->left_margin,
						$this->img->top_margin + 1,
						0,
						0,
						$this->img->plotwidth + 1,
						$this->img->plotheight,
						$bw,
						$bh,
						$this->background_image_mix
					);
				}

				break;
			case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
				$hadj = 0;
				$vadj = 0;
				if ($this->doshadow) {
					$hadj = $this->shadow_width;
					$vadj = $this->shadow_width;
				}
				$this->FillMarginArea();
				$this->FillPlotArea();
				$this->img->CopyMerge(
					$bkgimg,
					0,
					0,
					0,
					0,
					$this->img->width - $hadj,
					$this->img->height - $vadj,
					$bw,
					$bh,
					$this->background_image_mix
				);
				$this->StrokeFrame();

				break;
			case BGIMG_COPY: // Just copy the image from left corner, no resizing
				$this->FillMarginArea();
				$this->FillPlotArea();
				$this->img->CopyMerge(
					$bkgimg,
					0,
					0,
					0,
					0,
					$bw,
					$bh,
					$bw,
					$bh,
					$this->background_image_mix
				);
				$this->StrokeFrame();

				break;
			case BGIMG_CENTER: // Center original image in the plot area
				$this->FillMarginArea();
				$this->FillPlotArea();
				$centerx = round($this->img->plotwidth / 2 + $this->img->left_margin - $bw / 2);
				$centery = round($this->img->plotheight / 2 + $this->img->top_margin - $bh / 2);
				$this->img->CopyMerge(
					$bkgimg,
					$centerx,
					$centery,
					0,
					0,
					$bw,
					$bh,
					$bw,
					$bh,
					$this->background_image_mix
				);
				$this->StrokeFrame();

				break;
			case BGIMG_FREE: // Just copy the image to the specified location
				$this->img->CopyMerge(
					$bkgimg,
					$this->background_image_xpos,
					$this->background_image_ypos,
					0,
					0,
					$bw,
					$bh,
					$bw,
					$bh,
					$this->background_image_mix
				);
				$this->StrokeFrame(); // New
				break;
			default:
				Util\JpGraphError::RaiseL(25042); //(" Unknown background image layout");
		}
		$this->img->SetAngle($aa);
	}

	// Private
	// Draw a frame around the image
	public function StrokeFrame(){
		if (!$this->doframe) {
			return;
		}

		if ($this->background_image_type <= 1 && ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle == BGRAD_PLOT))) {
			$c = $this->margin_color;
		} else {
			$c = false;
		}

		if ($this->doshadow) {
			$this->img->SetColor($this->frame_color);
			$this->img->ShadowRectangle(
				0,
				0,
				$this->img->width,
				$this->img->height,
				$c,
				$this->shadow_width,
				$this->shadow_color
			);
		} elseif ($this->framebevel) {
			if ($c) {
				$this->img->SetColor($this->margin_color);
				$this->img->FilledRectangle(0, 0, $this->img->width - 1, $this->img->height - 1);
			}
			$this->img->Bevel(
				1,
				1,
				$this->img->width - 2,
				$this->img->height - 2,
				$this->framebeveldepth,
				$this->framebevelcolor1,
				$this->framebevelcolor2
			);
			if ($this->framebevelborder) {
				$this->img->SetColor($this->framebevelbordercolor);
				$this->img->Rectangle(0, 0, $this->img->width - 1, $this->img->height - 1);
			}
		} else {
			$this->img->SetLineWeight($this->frame_weight);
			if ($c) {
				$this->img->SetColor($this->margin_color);
				$this->img->FilledRectangle(0, 0, $this->img->width - 1, $this->img->height - 1);
			}
			$this->img->SetColor($this->frame_color);
			$this->img->Rectangle(0, 0, $this->img->width - 1, $this->img->height - 1);
		}
	}

	public function FillMarginArea(){
		$hadj = 0;
		$vadj = 0;
		if ($this->doshadow) {
			$hadj = $this->shadow_width;
			$vadj = $this->shadow_width;
		}

		$this->img->SetColor($this->margin_color);
		$this->img->FilledRectangle(0, 0, $this->img->width - 1 - $hadj, $this->img->height - 1 - $vadj);

		$this->img->FilledRectangle(0, 0, $this->img->width - 1 - $hadj, $this->img->top_margin);
		$this->img->FilledRectangle(0, $this->img->top_margin, $this->img->left_margin, $this->img->height - 1 - $hadj);
		$this->img->FilledRectangle(
			$this->img->left_margin + 1,
			$this->img->height - $this->img->bottom_margin,
			$this->img->width - 1 - $hadj,
			$this->img->height - 1 - $hadj
		);
		$this->img->FilledRectangle(
			$this->img->width - $this->img->right_margin,
			$this->img->top_margin + 1,
			$this->img->width - 1 - $hadj,
			$this->img->height - $this->img->bottom_margin - 1
		);
	}

	public function FillPlotArea(){
		$this->img->PushColor($this->plotarea_color);
		$this->img->FilledRectangle(
			$this->img->left_margin,
			$this->img->top_margin,
			$this->img->width - $this->img->right_margin,
			$this->img->height - $this->img->bottom_margin
		);
		$this->img->PopColor();
	}

	// Stroke the plot area with either a solid color or a background image
	public function StrokePlotArea(){
		// Note: To be consistent we really should take a possible shadow
		// into account. However, that causes some problem for the LinearScale class
		// since in the current design it does not have any links to class Graph which
		// means it has no way of compensating for the adjusted plotarea in case of a
		// shadow. So, until I redesign LinearScale we can't compensate for this.
		// So just set the two adjustment parameters to zero for now.
		$boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
		$adj	= 0; //$this->doshadow ? $this->shadow_width : 0 ;

		if ($this->background_image != '' || $this->background_cflag != '') {
			$this->StrokeFrameBackground();
		} else {
			$aa = $this->img->SetAngle(0);
			$this->StrokeFrame();
			$aa = $this->img->SetAngle($aa);
			$this->StrokeBackgroundGrad();
			if ($this->bkg_gradtype < 0 || ($this->bkg_gradtype > 0 && $this->bkg_gradstyle == BGRAD_MARGIN)) {
				$this->FillPlotArea();
			}
			$this->StrokePlotGrad();
		}
	}

	public function StrokeIcons(){
		$n = safe_count($this->iIcons);
		for ($i = 0; $i < $n; ++$i) {
			$this->iIcons[$i]->StrokeWithScale($this->img, $this->xscale, $this->yscale);
		}
	}

	public function StrokePlotBox(){
		// Should we draw a box around the plot area?
		if ($this->boxed) {
			$this->img->SetLineWeight(1);
			$this->img->SetLineStyle('solid');
			$this->img->SetColor($this->box_color);
			for ($i = 0; $i < $this->box_weight; ++$i) {
				$this->img->Rectangle(
					$this->img->left_margin - $i,
					$this->img->top_margin - $i,
					$this->img->width - $this->img->right_margin + $i,
					$this->img->height - $this->img->bottom_margin + $i
				);
			}
		}
	}

	public function SetTitleBackgroundFillStyle($aStyle, $aColor1 = 'black', $aColor2 = 'white'){
		$this->titlebkg_fillstyle = $aStyle;
		$this->titlebkg_scolor1   = $aColor1;
		$this->titlebkg_scolor2   = $aColor2;
	}

	public function SetTitleBackground($aBackColor = 'gray', $aStyle = TITLEBKG_STYLE1, $aFrameStyle = TITLEBKG_FRAME_NONE, $aFrameColor = 'black', $aFrameWeight = 1, $aBevelHeight = 3, $aEnable = true){
		$this->titlebackground			 = $aEnable;
		$this->titlebackground_color	   = $aBackColor;
		$this->titlebackground_style	   = $aStyle;
		$this->titlebackground_framecolor  = $aFrameColor;
		$this->titlebackground_framestyle  = $aFrameStyle;
		$this->titlebackground_frameweight = $aFrameWeight;
		$this->titlebackground_bevelheight = $aBevelHeight;
	}

	public function StrokeTitles(){
		$margin = 8;

		if ($this->titlebackground) {
			// Find out height
			$this->title->margin += 2;
			$h = $this->title->GetTextHeight($this->img) + $this->title->margin + $margin;
			if ($this->subtitle->t != '' && !$this->subtitle->hide) {
				$h += $this->subtitle->GetTextHeight($this->img) + $margin +
				$this->subtitle->margin;
				$h += 2;
			}
			if ($this->subsubtitle->t != '' && !$this->subsubtitle->hide) {
				$h += $this->subsubtitle->GetTextHeight($this->img) + $margin +
				$this->subsubtitle->margin;
				$h += 2;
			}
			$this->img->PushColor($this->titlebackground_color);
			if ($this->titlebackground_style === TITLEBKG_STYLE1) {
				// Inside the frame
				if ($this->framebevel) {
					$x1 = $y1 = $this->framebeveldepth + 1;
					$x2 = $this->img->width - $this->framebeveldepth - 2;
					$this->title->margin += $this->framebeveldepth + 1;
					$h += $y1;
					$h += 2;
				} else {
					$x1 = $y1 = $this->frame_weight;
					$x2 = $this->img->width - $this->frame_weight - 1;
				}
			} elseif ($this->titlebackground_style === TITLEBKG_STYLE2) {
				// Cover the frame as well
				$x1 = $y1 = 0;
				$x2 = $this->img->width - 1;
			} elseif ($this->titlebackground_style === TITLEBKG_STYLE3) {
				// Cover the frame as well (the difference is that
				// for style==3 a bevel frame border is on top
				// of the title background)
				$x1 = $y1 = 0;
				$x2 = $this->img->width - 1;
				$h += $this->framebeveldepth;
				$this->title->margin += $this->framebeveldepth;
			} else {
				Util\JpGraphError::RaiseL(25043); //('Unknown title background style.');
			}

			if ($this->titlebackground_framestyle === 3) {
				$h += $this->titlebackground_bevelheight * 2 + 1;
				$this->title->margin += $this->titlebackground_bevelheight;
			}

			if ($this->doshadow) {
				$x2 -= $this->shadow_width;
			}

			$indent = 0;
			if ($this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL) {
				$indent = $this->titlebackground_bevelheight;
			}

			if ($this->titlebkg_fillstyle == TITLEBKG_FILLSTYLE_HSTRIPED) {
				$this->img->FilledRectangle2(
					$x1 + $indent,
					$y1 + $indent,
					$x2 - $indent,
					$h - $indent,
					$this->titlebkg_scolor1,
					$this->titlebkg_scolor2
				);
			} elseif ($this->titlebkg_fillstyle == TITLEBKG_FILLSTYLE_VSTRIPED) {
				$this->img->FilledRectangle2(
					$x1 + $indent,
					$y1 + $indent,
					$x2 - $indent,
					$h - $indent,
					$this->titlebkg_scolor1,
					$this->titlebkg_scolor2,
					2
				);
			} else {
				// Solid fill
				$this->img->FilledRectangle($x1, $y1, $x2, $h);
			}
			$this->img->PopColor();

			$this->img->PushColor($this->titlebackground_framecolor);
			$this->img->SetLineWeight($this->titlebackground_frameweight);
			if ($this->titlebackground_framestyle == TITLEBKG_FRAME_FULL) {
				// Frame background
				$this->img->Rectangle($x1, $y1, $x2, $h);
			} elseif ($this->titlebackground_framestyle == TITLEBKG_FRAME_BOTTOM) {
				// Bottom line only
				$this->img->Line($x1, $h, $x2, $h);
			} elseif ($this->titlebackground_framestyle == TITLEBKG_FRAME_BEVEL) {
				$this->img->Bevel($x1, $y1, $x2, $h, $this->titlebackground_bevelheight);
			}
			$this->img->PopColor();

			// This is clumsy. But we neeed to stroke the whole graph frame if it is
			// set to bevel to get the bevel shading on top of the text background
			if ($this->framebevel && $this->doframe && $this->titlebackground_style === 3) {
				$this->img->Bevel(
					1,
					1,
					$this->img->width - 2,
					$this->img->height - 2,
					$this->framebeveldepth,
					$this->framebevelcolor1,
					$this->framebevelcolor2
				);
				if ($this->framebevelborder) {
					$this->img->SetColor($this->framebevelbordercolor);
					$this->img->Rectangle(0, 0, $this->img->width - 1, $this->img->height - 1);
				}
			}
		}

		// Stroke title
		$y = $this->title->margin;
		if ($this->title->halign == 'center') {
			$this->title->Center(0, $this->img->width, $y);
		} elseif ($this->title->halign == 'left') {
			$this->title->SetPos($this->title->margin + 2, $y);
		} elseif ($this->title->halign == 'right') {
			$indent = 0;
			if ($this->doshadow) {
				$indent = $this->shadow_width + 2;
			}
			$this->title->SetPos($this->img->width - $this->title->margin - $indent, $y, 'right');
		}
		$this->title->Stroke($this->img);

		// ... and subtitle
		$y += $this->title->GetTextHeight($this->img) + $margin + $this->subtitle->margin;
		if ($this->subtitle->halign == 'center') {
			$this->subtitle->Center(0, $this->img->width, $y);
		} elseif ($this->subtitle->halign == 'left') {
			$this->subtitle->SetPos($this->subtitle->margin + 2, $y);
		} elseif ($this->subtitle->halign == 'right') {
			$indent = 0;
			if ($this->doshadow) {
				$indent = $this->shadow_width + 2;
			}

			$this->subtitle->SetPos($this->img->width - $this->subtitle->margin - $indent, $y, 'right');
		}
		$this->subtitle->Stroke($this->img);

		// ... and subsubtitle
		$y += $this->subtitle->GetTextHeight($this->img) + $margin + $this->subsubtitle->margin;
		if ($this->subsubtitle->halign == 'center') {
			$this->subsubtitle->Center(0, $this->img->width, $y);
		} elseif ($this->subsubtitle->halign == 'left') {
			$this->subsubtitle->SetPos($this->subsubtitle->margin + 2, $y);
		} elseif ($this->subsubtitle->halign == 'right') {
			$indent = 0;
			if ($this->doshadow) {
				$indent = $this->shadow_width + 2;
			}

			$this->subsubtitle->SetPos($this->img->width - $this->subsubtitle->margin - $indent, $y, 'right');
		}
		$this->subsubtitle->Stroke($this->img);

		// ... and fancy title
		$this->tabtitle->Stroke($this->img);
	}

	public function StrokeTexts(){
		// Stroke any user added text objects
		if ($this->texts != null) {
			for ($i = 0; $i < safe_count($this->texts); ++$i) {
				$this->texts[$i]->StrokeWithScale($this->img, $this->xscale, $this->yscale);
			}
		}

		if ($this->y2texts != null && $this->y2scale != null) {
			for ($i = 0; $i < safe_count($this->y2texts); ++$i) {
				$this->y2texts[$i]->StrokeWithScale($this->img, $this->xscale, $this->y2scale);
			}
		}
	}

	public function StrokeTables(){
		if ($this->iTables != null) {
			$n = safe_count($this->iTables);
			for ($i = 0; $i < $n; ++$i) {
				$this->iTables[$i]->StrokeWithScale($this->img, $this->xscale, $this->yscale);
			}
		}
	}

	public function DisplayClientSideaImageMapAreas(){
		// Debug stuff - display the outline of the image map areas
		$csim = '';
		foreach ($this->plots as $p) {
			$csim .= $p->GetCSIMareas();
		}
		$csim .= $this->legend->GetCSIMareas();
		if (preg_match_all('/area shape="(\\w+)" coords="([0-9\\, ]+)"/', $csim, $coords)) {
			$this->img->SetColor($this->csimcolor);
			$n = safe_count($coords[0]);
			for ($i = 0; $i < $n; ++$i) {
				if ($coords[1][$i] == 'poly') {
					preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/', $coords[2][$i], $pts);
					$this->img->SetStartPoint($pts[1][count($pts[0]) - 1], $pts[2][count($pts[0]) - 1]);
					$m = safe_count($pts[0]);
					for ($j = 0; $j < $m; ++$j) {
						$this->img->LineTo($pts[1][$j], $pts[2][$j]);
					}
				} elseif ($coords[1][$i] == 'rect') {
					$pts = preg_split('/,/', $coords[2][$i]);
					$this->img->SetStartPoint($pts[0], $pts[1]);
					$this->img->LineTo($pts[2], $pts[1]);
					$this->img->LineTo($pts[2], $pts[3]);
					$this->img->LineTo($pts[0], $pts[3]);
					$this->img->LineTo($pts[0], $pts[1]);
				}
			}
		}
	}

	// Text scale offset in world coordinates
	public function SetTextScaleOff($aOff){
		$this->text_scale_off		 = $aOff;
		$this->xscale->text_scale_off = $aOff;
	}

	// Text width of bar to be centered in absolute pixels
	public function SetTextScaleAbsCenterOff($aOff){
		$this->text_scale_abscenteroff = $aOff;
	}

	// Get Y min and max values for added lines
	public function GetLinesYMinMax($aLines){
		if (is_null($aLines)) {
			return false;
		}

		$n = safe_count($aLines);
		if ($n == 0) {
			return false;
		}

		$min = $aLines[0]->scaleposition;
		$max = $min;
		$flg = false;
		for ($i = 0; $i < $n; ++$i) {
			if ($aLines[$i]->direction == HORIZONTAL) {
				$flg = true;
				$v   = $aLines[$i]->scaleposition;
				if ($min > $v) {
					$min = $v;
				}

				if ($max < $v) {
					$max = $v;
				}
			}
		}

		return $flg ? [$min, $max] : false;
	}

	// Get X min and max values for added lines
	public function GetLinesXMinMax($aLines){
		$n = safe_count($aLines);
		if ($n == 0) {
			return false;
		}

		$min = $aLines[0]->scaleposition;
		$max = $min;
		$flg = false;
		for ($i = 0; $i < $n; ++$i) {
			if ($aLines[$i]->direction == VERTICAL) {
				$flg = true;
				$v   = $aLines[$i]->scaleposition;
				if ($min > $v) {
					$min = $v;
				}

				if ($max < $v) {
					$max = $v;
				}
			}
		}

		return $flg ? [$min, $max] : false;
	}

	// Get min and max values for all included plots
	public function GetPlotsYMinMax($bPlots){
		$aPlots = array_filter($bPlots, function ($plot) {
			//\Kint::dump($plot, $plot instanceof Plot\Plot);
			return $plot instanceof Plot\Plot;
		});
		reset($aPlots);
		$n = safe_count($aPlots);
		$i = 0;
		//\Kint::dump($n, $aPlots);
		do {
			//\Kint::dump($i, $aPlots[$i]);
			list($xmax, $max) = isset($aPlots[$i]) ? $aPlots[$i]->Max() : [null, null];
		} while (++$i < $n && !is_numeric($max));

		$i = 0;
		do {
			list($xmin, $min) = isset($aPlots[$i]) ? $aPlots[$i]->Min() : [null, null];
		} while (++$i < $n && !is_numeric($min));

		if (!is_numeric($min) || !is_numeric($max)) {
			Util\JpGraphError::RaiseL(25044); //('Cannot use autoscaling since it is impossible to determine a valid min/max value  of the Y-axis (only null values).');
		}

		for ($i = 0; $i < $n; ++$i) {
			list($xmax, $ymax) = isset($aPlots[$i]) ? $aPlots[$i]->Max() : [null, null];
			list($xmin, $ymin) = isset($aPlots[$i]) ? $aPlots[$i]->Min() : [null, null];
			if (is_numeric($ymax)) {
				$max = max($max, $ymax);
			}

			if (is_numeric($ymin)) {
				$min = min($min, $ymin);
			}
		}
		if ($min == '') {
			$min = 0;
		}

		if ($max == '') {
			$max = 0;
		}

		if ($min == 0 && $max == 0) {
			// Special case if all values are 0
			$min = 0;
			$max = 1;
		}

		return [$min, $max];
	}

	public function hasLinePlotAndBarPlot(){
		$has_line = false;
		$has_bar  = false;

		foreach ($this->plots as $plot) {
			if ($plot instanceof Plot\LinePlot) {
				$has_line = true;
			}
			if ($plot instanceof Plot\BarPlot) {
				$has_bar = true;
			}
		}

		if ($has_line && $has_bar) {
			return true;
		}

		return false;
	}

	public function SetTheme($graph_theme){
		if (!($this instanceof PieGraph)) {
			if (!$this->isAfterSetScale) {
				Util\JpGraphError::RaiseL(25133); //('Use Graph::SetTheme() after Graph::SetScale().');
			}
		}

		if ($this->graph_theme) {
			$this->ClearTheme();
		}
		$this->graph_theme = $graph_theme;
		$this->graph_theme->ApplyGraph($this);
	}

	public function ClearTheme(){
		$this->graph_theme = null;

		$this->isRunningClear = true;

		$this->__construct(
			$this->inputValues['aWidth'],
			$this->inputValues['aHeight'],
			$this->inputValues['aCachedName'],
			$this->inputValues['aTimeout'],
			$this->inputValues['aInline']
		);

		if (!($this instanceof PieGraph)) {
			if ($this->isAfterSetScale) {
				$this->SetScale(
					$this->inputValues['aAxisType'],
					$this->inputValues['aYMin'],
					$this->inputValues['aYMax'],
					$this->inputValues['aXMin'],
					$this->inputValues['aXMax']
				);
			}
		}

		$this->isRunningClear = false;
	}

	public function SetSupersampling($do = false, $scale = 2){
		if ($do) {
			define('SUPERSAMPLING_SCALE', $scale);
		// $this->img->scale = $scale;
		} else {
			define('SUPERSAMPLING_SCALE', 1);
			//$this->img->scale = 0;
		}
	}
} // @class
